// Copyright 2023 The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import mockRangeQuery from '../../data/mock-query-range.json';

type ExampleMetric = {
  __name__?: string;
  instance?: string;
  job?: string;
  le?: string;
};

type Datapoint = [number, string];

type SeriesData = {
  metric: ExampleMetric;
  values: Datapoint[];
};

type MockResponse = {
  status: string;
  data: Record<string, unknown>;
};

type MockStableValueResultConfig = {
  metric: Record<string, string>;
  value: string;
};

interface MockTimeSeriesValueConfig {
  startTimeMs: number;
  endTimeMs: number;
  totalDatapoints?: number;
}

interface MockStableValueConfig extends MockTimeSeriesValueConfig {
  metrics: MockStableValueResultConfig[];
}

interface MockManySeriesConfig extends MockTimeSeriesValueConfig {
  totalSeries?: number;
}

function mockSuccessfulTimeSeriesResponse(result: SeriesData[]): MockResponse {
  return {
    status: 'success',
    data: {
      resultType: 'matrix',
      result,
    },
  };
}

export function mockTimeSeriesResponseWithStableValue({
  metrics,
  startTimeMs,
  endTimeMs,
  totalDatapoints = 1000,
}: MockStableValueConfig): MockResponse {
  // Taking time in as milliseconds because that's the unit generated by Date.now
  // in JS (i.e. mostly what the test code is going). Converting to seconds because
  // that's the unit used in prometheus responses.
  const startTimeS = Math.floor(startTimeMs / 1000);
  const endTimeS = Math.floor(endTimeMs / 1000);
  const stepSize = Math.floor((endTimeS - startTimeS) / totalDatapoints);

  return mockSuccessfulTimeSeriesResponse(
    metrics.map(({ metric, value }) => {
      return {
        metric,
        values: [...Array(totalDatapoints)].map((_, i) => {
          // Use the end time for the last item to make sure we include it in
          // cases where the step size would lead to a lower value because of
          // rounding.
          const timestamp = i < totalDatapoints - 1 ? startTimeS + i * stepSize : endTimeS;

          return [timestamp, value];
        }),
      };
    })
  );
}

/*
 * Testing TimeSeriesQuery plugins that can return null values
 */
export function mockTimeSeriesResponseWithNullValues({
  startTimeMs,
  endTimeMs,
  totalDatapoints = 1000,
}: MockTimeSeriesValueConfig): MockResponse {
  const startTimeS = Math.floor(startTimeMs / 1000);
  const endTimeS = Math.floor(endTimeMs / 1000);
  const stepSize = Math.floor((endTimeS - startTimeS) / totalDatapoints);

  return mockSuccessfulTimeSeriesResponse([
    {
      metric: {},
      values: [...Array(totalDatapoints)].map((_, i) => {
        const timestamp = i < totalDatapoints - 1 ? startTimeS + i * stepSize : endTimeS;
        let value: string | null = '100';
        // to test visual.connectNulls option
        if (i > 50 && i < 100) {
          value = null;
        }
        // TODO: fix types when graphite datasource added
        return [timestamp, value as string];
      }),
    },
  ]);
}

/*
 * Mock output using example data from queries:
 * rpc_durations_histogram_seconds_bucket
 * rate(caddy_http_request_duration_seconds_bucket[$interval])
 */
export function mockTimeSeriesResponseWithManySeries({
  startTimeMs,
  endTimeMs,
  totalSeries = 16,
  totalDatapoints = 1000,
}: MockManySeriesConfig): MockResponse {
  const startTimeS = Math.floor(startTimeMs / 1000);
  const endTimeS = Math.floor(endTimeMs / 1000);
  const stepSize = Math.floor((endTimeS - startTimeS) / totalDatapoints);

  const mockData: SeriesData[] = [];

  // Test data cycles back and forth depending on whether the series index is an even
  const exampleJobs = ['node', 'caddy'];
  const exampleMetric = ['rpc_durations_histogram_seconds_bucket', 'caddy_http_request_duration_seconds_bucket'];
  const exampleInstances = ['demo.do.prometheus.io:8996', 'rc-demo-environment/data/prom'];

  const GAP_BETWEEN_SERIES_MULTIPLIER = totalSeries * 1000;
  const STATIC_GAP = 10;

  for (let i = 0; i < totalSeries; i++) {
    const exampleIndex = i % 2;
    const metric: ExampleMetric = {
      __name__: exampleMetric[exampleIndex],
      instance: exampleInstances[exampleIndex],
      job: exampleJobs[exampleIndex],
      le: `${i}.${exampleIndex}`,
    };

    const values: Datapoint[] = [];

    for (let j = 0; j < totalDatapoints; j++) {
      const TREND_UPWARDS_INCREMENT = j * 30;
      const timestamp = j < totalDatapoints - 1 ? startTimeS + j * stepSize : endTimeS;
      // TODO: better approach for realistic trends for test data
      const testValue = i * GAP_BETWEEN_SERIES_MULTIPLIER + STATIC_GAP + TREND_UPWARDS_INCREMENT;
      values.push([timestamp, String(1000 + testValue)]);
    }

    mockData.push({ metric, values });
  }
  return mockSuccessfulTimeSeriesResponse(mockData);
}

/*
 * Hard-coded mock response using test data
 */
export function mockTimeSeriesResponseWithTestData(): MockResponse {
  return mockSuccessfulTimeSeriesResponse(mockRangeQuery.data.result as unknown as SeriesData[]);
}
